/*

* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Smart-Shop开源商城系统/ Java商城-首选电商平台系统 请务必保留此注释头信息
* |     开源地址: https://gitee.com/ningbo-qishan/gruul-mall
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     可正常分享和学习源码,未经授权 不可商用！
* |     商业版中台购买/开源版商业授权 联系技术客服
* |     官网:  https://www.73app.cn/
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Copyright (c) 2016-2999 宁波启山智软科技有限公司
* |     版权所有 ,侵权必究！
* ------------------------------------------------------------------------

*/
package com.medusa.gruul.platform.service.impl;

import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.medusa.gruul.common.core.auth.client.helper.ISecurity;
import com.medusa.gruul.common.core.auth.model.constant.Keys;
import com.medusa.gruul.common.core.config.Jackson;
import com.medusa.gruul.common.core.constant.TimeConstants;
import com.medusa.gruul.common.core.exception.ServiceException;
import com.medusa.gruul.common.core.helper.RedisKeyHelper;
import com.medusa.gruul.common.core.util.SystemCode;
import com.medusa.gruul.common.redis.RedisManager;
import com.medusa.gruul.platform.api.entity.MiniInfo;
import com.medusa.gruul.platform.api.enums.WechatLangEnum;
import com.medusa.gruul.platform.api.enums.WxScopeEnum;
import com.medusa.gruul.platform.api.model.dto.LoginDto;
import com.medusa.gruul.platform.api.model.dto.WechatMiniLoginDTO;
import com.medusa.gruul.platform.conf.PlatformRedis;
import com.medusa.gruul.platform.conf.WxMpProperties;
import com.medusa.gruul.platform.model.dto.multi.MobileEncryptDataDTO;
import com.medusa.gruul.platform.service.IMiniInfoService;
import com.medusa.gruul.platform.service.IMultiClientWechatService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.open.api.WxOpenMaService;
import me.chanjar.weixin.open.api.WxOpenService;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @author 张治保
 * date 2021/11/27
 */
@Slf4j
@Service
public class MultiClientWechatServiceImpl implements IMultiClientWechatService {

    private final IMiniInfoService miniInfoService;
    private final WxMpService wxMpService;
    private final WxOpenService wxOpenService;
    private final WxMpProperties.MultiClientConfig app;

    public MultiClientWechatServiceImpl(IMiniInfoService miniInfoService, WxMpProperties wxMpProperties, WxMpService wxMpService, WxOpenService wxOpenService) {
        this.miniInfoService = miniInfoService;
        this.wxMpService = wxMpService;
        this.app = wxMpProperties.getMultiClientConfig();
        this.wxOpenService = wxOpenService;
    }

    @Override
    public String openQrcodeUrl(String successUrl) {
        String state = UUID.fastUUID().toString(true);
        /*
         * 记录跳转url
         */
        PlatformRedis.instance().setNxPx(state, successUrl, TimeConstants.TEN_MINUTES);

        return wxMpService.switchoverTo(app.getAppid())
            .buildQrConnectUrl(
                StrUtil.concat(true, app.getDomain(), "/multi/wechat/login")
                ,
                WxScopeEnum.SNSAPI_LOGIN.value(),
                state
            );
    }

    @Override
    public String openLogin(String code, String state) {
        String successUrl = PlatformRedis.instance().get(state);
        if (StrUtil.isBlank(successUrl)) {
            throw new ServiceException("无效数据或数据已过期");
        }
        WxMpOAuth2AccessToken accessToken = this.getAccessToken(code);
        /*
         * 缓存用户信息
         */
        String loginId = IdUtil.simpleUUID();
        WxMpUser mpUser = this.getWxMpUser(accessToken);
        RedisManager.getInstance().setNxPx(
            RedisKeyHelper.key(Keys.USER_LOGIN,loginId),
            Jackson.writeValueAsString(mpUser),
            TimeConstants.TEN_MINUTES
        );
        String loginIdParam = "loginId = ".concat(loginId);

        return successUrl.contains("?") ? successUrl.concat(loginIdParam) : successUrl.concat("?").concat(loginIdParam);
    }


    @Override
    public WechatMiniLoginDTO miniLogin(String code) {
        MiniInfo miniInfo = miniInfoService.getByMiniTenantId(ISecurity.tenantId().must());
        String appId;
        if(miniInfo == null || StrUtil.isBlank(appId = miniInfo.getAppId())){
            throw new ServiceException(SystemCode.DATA_NOT_EXIST);
        }
        WxOpenMaService maService = wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appId);

        WxMaJscode2SessionResult result;
        try {
            result = maService.jsCode2SessionInfo(code);
        } catch (WxErrorException e) {
            throw new ServiceException(e.getError().getErrorMsg(), e.getCause());
        }
        String sessionKey = result.getSessionKey();
        RedisManager.getInstance().set(
            RedisKeyHelper.key(Keys.USER_LOGIN,"session",sessionKey),
            Jackson.writeValueAsString(
                new LoginDto().setSessionKey(result.getSessionKey()).setOpenId(result.getOpenid()).setUnionId(result.getUnionid())
            ),
            (int)TimeUnit.MILLISECONDS.toSeconds(TimeConstants.TEN_MINUTES)
        );
        return new WechatMiniLoginDTO()
            .setSessionKey(sessionKey);
    }

    @Override
    public String decryptMobile(MobileEncryptDataDTO encryptData) {
        String loginInfo = RedisManager.getInstance().get(
            RedisKeyHelper.key(Keys.USER_LOGIN,"session",encryptData.getSessionKey())
        );
        if (StrUtil.isBlank(loginInfo)){
            throw new ServiceException("缓存数据已失效");
        }
        /*
         * 缓存中读取已存储的数据 
         */
        LoginDto loginDto = Jackson.readValue(loginInfo, LoginDto.class);

        /*
         * 解密数据
         */
        String json = WxMaCryptUtils.decrypt(encryptData.getSessionKey(), encryptData.getEncryptedData(), encryptData.getIv());
        if (StrUtil.isBlank(json)){
            throw new ServiceException(SystemCode.REQ_REJECT);
        }
        log.info("解密微信小程序用户数据:{}",json);
        JSONObject jsonObject;
        try{
            jsonObject = JSON.parseObject(json);
        }catch (Exception e){
            throw new ServiceException(SystemCode.PARAM_VALID_ERROR);
        }
        String mobile = jsonObject.getString("purePhoneNumber");
        if (StrUtil.isBlank(mobile)){
            throw new ServiceException(SystemCode.DATA_NOT_EXIST);
        }
        /*
         * 设置手机号 并把登录信息设置到缓存
         */
        loginDto.setMobile(mobile);
        String loginId = IdUtil.simpleUUID();
        RedisManager.getInstance().set(
            RedisKeyHelper.key(Keys.USER_LOGIN,loginId),
            Jackson.writeValueAsString(loginDto),
            (int)TimeUnit.MILLISECONDS.toSeconds(TimeConstants.TEN_MINUTES)
        );
        return loginId;
    }



    private WxMpUser getWxMpUser(WxMpOAuth2AccessToken accessToken) {
        try {
            return wxMpService.switchoverTo(app.getAppid()).oauth2getUserInfo(accessToken, WechatLangEnum.ZH_CN.getValue());
        } catch (WxErrorException e) {
            throw new ServiceException(e.getError().getErrorMsg());
        }
    }

    private WxMpOAuth2AccessToken getAccessToken(String code) {
        WxMpOAuth2AccessToken oAuth2AccessToken;
        try {
            oAuth2AccessToken = wxMpService.switchoverTo(app.getAppid()).oauth2getAccessToken(code);
        } catch (WxErrorException e) {
            e.printStackTrace();
            log.info("用code换取accesstoken是失败");
            throw new ServiceException(e.getError().getErrorMsg());
        }
        return oAuth2AccessToken;
    }

}
